package com.cug.campus_sh_market_server.util;


import lombok.extern.slf4j.Slf4j;

import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;

/**
 * @description object转对象的工具类
 * @Author ttxxi
 * @Date 2021.03.12
 */
@Slf4j
public class MappingUtil {

    /**
     * 对象转换为对应的实体类
     *
     * @param <SRC> 原对象类文件
     * @param <DES> 目标类类文件
     * @return 转换后的实体类对象
     */
    @SuppressWarnings("ConstantConditions")
    public static <SRC, DES> DES getMappingObject(SRC src, Class<DES> desClass) {
        try {

            DES des = desClass.newInstance();

            Method[] declaredMethods = desClass.getDeclaredMethods();
            Stream<Method> setMethods = Arrays.stream(declaredMethods).filter(item -> item.getName().startsWith("set"));

            setMethods.forEach(method -> {
                //获取字段名
                String methodName = method.getName();
                /*首字母大写*/
                String fieldNameBig = methodName.substring(methodName.indexOf("set") + "set".length());
                /*首字母小写*/
                String fieldName = fieldNameBig.substring(0, 1).toLowerCase() + fieldNameBig.substring(1);

                //set中存放对应field可能的别名(均是首字母小写)
                Set<String> valueSet = new HashSet<>();
                //默认在比较时添加自身名称到set中,所以如果没有注解则默认按照自身名称获取值
                valueSet.add(fieldName);
                //获取注解并取得到values
                Mapping annotation = getFieldAnnotation(fieldName, desClass, Mapping.class);
                if (annotation != null) {
                    String[] values = annotation.value();
                    if (values != null && values.length != 0) {
                        valueSet.addAll(Arrays.asList(values));
                    }
                }
                Class<?> srcClass = src.getClass();
                //逐一确定是否匹配object
                for (String value : valueSet) {

                    //首字母大写
                    value = value.substring(0, 1).toUpperCase() + value.substring(1);

                    Method getValueMethod = null;
                    try {
                        getValueMethod = srcClass.getDeclaredMethod("get" + value, null);
                        log.info(getValueMethod.toString());
                    } catch (NoSuchMethodException e) {
                        log.error("入参【{}】没有【{}】方法，无法为字段【{}】赋值",srcClass.getName(), "get" + value, fieldName);
                        return;
                    }

                    Object o = null;
                    try {
                        o = getValueMethod.invoke(src);
                        //如果匹配，则执行方法并跳出循环
                        if (o != null) {
                            method.invoke(des, o);
                            //跳出当前循环
                            break;
                        }
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        log.error("调用MappingUtil反射执行【{}】失败,参数为【{}】", method.getName(), o);
                        throw new RuntimeException("解析json失败",e);
                    }

                }

            });

            return des;

        } catch (IllegalAccessException | InstantiationException e) {
            log.error("调用MappingUtil反射创建【{}】失败", desClass.getName());
            throw new RuntimeException("解析json失败",e);
        }

    }


    /**
     * 获取一个类的字段是否标的某个特定的注解
     *
     * @param fieldName   字段名
     * @param tClass      目标检测类
     * @param eAnnotation 目标检测注解类
     * @return 获取到的注解
     */
    private static <E extends Annotation> E getFieldAnnotation(String fieldName, Class tClass, Class<E> eAnnotation) {
        Field targetField = null;
        try {
            targetField = tClass.getDeclaredField(fieldName);
        } catch (NoSuchFieldException e) {
            return null;
        }
        return targetField.getAnnotation(eAnnotation);
    }


    /**
     * 用来标记可以转换的目标字段，支持多个属性，必须是驼峰命名
     */
    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Mapping {
        String[] value();
    }


}